package com.dhz.common.base.utils;

import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>线程安全、高性能的LRUCacheMap实现</p>
 *
 * <pre>
 *     1. 采用分区策略(segment)提升并发性能 - by Doug Lea
 *     2. 为缓存专门设计的SoftReference包装，避免Cache引起JVM的OOM
 * </pre>
 *
 * 资源来自：http://code.taobao.org/p/taobao-pamirs-proxycache/src/trunk/src/main/java/com/taobao/pamirs/cache/util/lru/ConcurrentLRUCacheMap.java
 */
public class ConcurrentLRUCacheMap<K, V> implements Serializable {

    /** 默认大小: 10000000000 - 1024(byte) */
    public static final int DEFAULT_INITIAL_CAPACITY = 1 << 10;
    /** 默认的分区数量: 10000 - 16(byte) */
    public static final int DEFAULT_CONCURRENCY_LEVEL = 1 << 4;
    /** 最大容量: 1000000000000000000000000000000 - 1073741824(byte) */
    static final int MAX_CAPACITY = 1 << 30;
    /** 最大最大的切片分区数量: 10000000000000000 - 65536 */
    static final int MAX_SEGMENTS = 1 << 16;

    /** 该只用于确定使用那个分区 */
    private final int segmentMask;

    /** 分区内的索引位移值 */
    private final int segmentShift;

    private LRUMapLocked<K, SoftReference<V>, V>[] segments;

    /**
     * 默认构造器
     */
    public ConcurrentLRUCacheMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_CONCURRENCY_LEVEL);
    }

    public ConcurrentLRUCacheMap(int size) {
        this(size, DEFAULT_CONCURRENCY_LEVEL);
    }

    /**
     * <p>推荐构造函数</p>
     * @param size 必须能被segmentSize整除
     * @param segmentSize 必须2的倍数
     */
    @SuppressWarnings("unchecked")
    public ConcurrentLRUCacheMap(int size, int segmentSize) {
        if (size < 0 || segmentSize <= 0) {
            throw new IllegalArgumentException();
        }

        if (segmentSize > MAX_SEGMENTS)
            segmentSize = MAX_SEGMENTS;

        int _shift = 0;
        int _size = 1; // 分区大小：2的倍数
        while (_size < segmentSize) {
            ++_shift;
            _size <<= 1; // 左移一位
        }

        if (_size != segmentSize) {
            throw new IllegalArgumentException("分区SIZE必须是2的倍数");
        }

        segmentShift = 32 - _shift;
        segmentMask = _size - 1;
        segments = new LRUMapLocked[_size];

        if (size > MAX_CAPACITY)
            size = MAX_CAPACITY;
        int c = size / _size;
        if (c * _size != size)
            throw new IllegalArgumentException(
                    "size must divide exactly for segmentSize!");
        if (c * _size < size)
            ++c;
        int cap = 1;// 平摊到每个分区Map的size
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new LRUMapLocked<K, SoftReference<V>, V>(cap);

    }

    // ----------------- Cache Opt Method --------------------------

    public V get(K key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).getEntry(key);
    }

    public void put(K key, V value) {
        if (value == null) {
            throw new NullPointerException();
        }

        int hash = hash(key.hashCode());
        segmentFor(hash).addEntry(key, value);
    }

    public void remove(K key) {
        int hash = hash(key.hashCode());
        segmentFor(hash).remove(key);
    }

    public synchronized void clear() {
        for (LRUMapLocked<K, SoftReference<V>, V> segment : this.segments) {
            segment.clear();
        }
    }

    public int size() {
        final LRUMapLocked<K, SoftReference<V>, V>[] segments = this.segments;
        long sum = 0;
        for (LRUMapLocked<K, SoftReference<V>, V> segment : segments) {
            sum += segment.size();
        }

        if (sum > Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        } else {
            return (int) sum;
        }
    }


    // ----------------- base method -----------------------

    /**
     * Applies a supplemental hash function to a given hashCode, which defends
     * against poor quality hash functions. This is critical because
     * ConcurrentHashMap uses power-of-two length hash tables, that otherwise
     * encounter collisions for hashCodes that do not differ in lower or upper
     * bits.
     */
    private static int hash(int h) {
        // Spread bits to regularize both segment and index locations,
        // using variant of single-word Wang/Jenkins hash.
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }

    /**
     * Returns the segment that should be used for key with given hash
     *
     * @param hash the hash code for the key
     * @return the segment
     */
    private LRUMapLocked<K, SoftReference<V>, V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }


    public static class LRUMapLocked<KK, TT extends SoftReference<VV>, VV> extends LRUMap<KK, TT> {
        /** map 锁 */
        private final Lock lock = new ReentrantLock();

        public LRUMapLocked() {
            super();
        }

        public LRUMapLocked(int size) {
            super(size);
        }

        /**
         * <b>添加元素 - 线程安全.</b> 代替{@link #put(Object, Object)}方法；
         * @param key key
         * @param entry value
         */
        @SuppressWarnings("unchecked")
        public void addEntry(KK key, VV entry) {
            lock.lock();

            try {
                SoftReference<VV> sr_entry = new SoftReference<VV>(entry);
                super.put(key, (TT) entry);
            } finally {
                lock.unlock();
            }
        }

        /**
         * <b>获取元素 - 线程安全.</b> 代替{@link #get(Object)}方法；
         * @param key key
         * @return entry
         */
        @SuppressWarnings("unchecked")
        public VV getEntry(KK key) {
            lock.lock();
            try {
                SoftReference<TT> sr_entry = (SoftReference<TT>) get(key);
                if (sr_entry == null) return null;

                if (sr_entry.get() == null) {
                    super.remove(key);
                    return null;
                }

                return (VV) sr_entry.get();
            } finally {
                lock.unlock();
            }

        }

        /**
         * <b>删除元素 - 线程安全.</b> 代替{@link super#remove(Object)} 方法；
         * @param key key
         * @return entry
         */
        @Override
        public TT remove(Object key) {
            lock.lock();

            try {
                return super.remove(key);
            } finally {
                lock.unlock();
            }
        }

        /**
         * <b>清空元素 - 线程安全.</b> 代替{@link super#clear()} 方法；
         */
        @Override
        public void clear() {
            lock.lock();
            try {
                super.clear();
            } finally {
                lock.unlock();
            }
        }
    }

}
